"""
python p2_solution_deap_genetic_4t_doubao.py
"""


import os
import json
import random
from deap import base, creator, tools, algorithms

# 设置相对路径
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
WAREHOUSE_JSON_PATH = os.path.join(BASE_DIR, '../fujian/fujian3/origin_data/warehouse.json')
ALL_AVERAGE_INVENTORY_JSON_PATH = os.path.join(BASE_DIR, '../fujian/fujian3/data_from_p1/all_average_inventory.json')
ALL_AVERAGE_SALES_JSON_PATH = os.path.join(BASE_DIR, '../fujian/fujian3/data_from_p1/all_average_sales.json')
ORIGIN_DATA_JSON_PATH = os.path.join(BASE_DIR, '../fujian/fujian4/origin_data.json')
INITIAL_SOLUTION_PATH = os.path.join(BASE_DIR, '../fujian/p2/simple_solution_handwrite/ans.json')
OUTPUT_DIR = os.path.join(BASE_DIR, '../fujian/fujian3/deap_genetic_3')
BEST_EVER_DIR = os.path.join(OUTPUT_DIR, 'best_ever')
ALL_COST_FILE_PATH = os.path.join(OUTPUT_DIR, 'all_cost.txt')
ALL_UTILIZATION_RATE_INVENTORY_JSON_PATH = os.path.join(OUTPUT_DIR, 'all_utilization_rate_inventory.json')
ALL_UTILIZATION_RATE_SALES_JSON_PATH = os.path.join(OUTPUT_DIR, 'all_utilization_rate_sales.json')


def load_json_data(file_path):
    with open(file_path, 'r') as f:
        return json.load(f)


def save_json_data(data, file_path):
    with open(file_path, 'w') as f:
        json.dump(data, f, indent=4)


def calculate_fitness(individual):
    total_cost = 0
    total_inventory_utilization = 0
    total_sales_utilization = 0
    total_association = 0

    warehouse_inventories = {}
    warehouse_sales = {}

    for category_id, warehouse_id in enumerate(individual):
        warehouse_id = int(warehouse_id)
        warehouse_data = warehouses[warehouse_id]
        average_inventory = category_average_inventories[category_id]['average_inventory']
        average_sales = category_average_sales[category_id]['average_sales']

        if warehouse_id not in warehouse_inventories:
            warehouse_inventories[warehouse_id] = average_inventory
        else:
            warehouse_inventories[warehouse_id] += average_inventory

        if warehouse_id not in warehouse_sales:
            warehouse_sales[warehouse_id] = average_sales
        else:
            warehouse_sales[warehouse_id] += average_sales

    for warehouse_id, inventory in warehouse_inventories.items():
        warehouse_data = warehouses[warehouse_id]
        max_inventory = warehouse_data['max_inventory']
        max_sales = warehouse_data['max_sales']
        daily_cost = warehouse_data['daily_cost']

        if inventory > max_inventory or warehouse_sales[warehouse_id] > max_sales:
            return float('inf'), 0, 0, 0

        inventory_utilization = inventory / max_inventory
        total_inventory_utilization += inventory_utilization

        sales_utilization = warehouse_sales[warehouse_id] / max_sales
        total_sales_utilization += sales_utilization

        if warehouse_id not in used_warehouses:
            used_warehouses[warehouse_id] = True
            total_cost += daily_cost

    for warehouse_id in warehouse_inventories.keys():
        category_ids_in_warehouse = [category_id for category_id, w_id in enumerate(individual) if w_id == warehouse_id]
        for i in range(len(category_ids_in_warehouse)):
            for j in range(i + 1, len(category_ids_in_warehouse)):
                category_pair = tuple(sorted([category_ids_in_warehouse[i], category_ids_in_warehouse[j]]))
                if category_pair in associations:
                    total_association += associations[category_pair]

    return total_cost, total_inventory_utilization, total_sales_utilization, total_association


def mutate(individual):
    category_id = random.randint(0, len(individual) - 1)
    new_warehouse_id = random.randint(0, len(warehouses) - 1)
    individual[category_id] = new_warehouse_id
    return individual,


creator.create("FitnessMulti", base.Fitness, weights=(-1, 1, 1, 1))
creator.create("Individual", list, fitness=creator.FitnessMulti)

toolbox = base.Toolbox()
toolbox.register("attr_int", random.randint, 0, len(warehouses) - 1)
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_int, n=len(category_average_inventories))
toolbox.register("population", tools.initRepeat, list, toolbox.individual)

toolbox.register("evaluate", calculate_fitness)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", mutate)
toolbox.register("select", tools.selTournament, tournsize=3)

warehouses = load_json_data(WAREHOUSE_JSON_PATH)
category_average_inventories = load_json_data(ALL_AVERAGE_INVENTORY_JSON_PATH)
category_average_sales = load_json_data(ALL_AVERAGE_SALES_JSON_PATH)
associations = {(data['category1'], data['category2']): data['association'] for data in load_json_data(ORIGIN_DATA_JSON_PATH)}

initial_solution = load_json_data(INITIAL_SOLUTION_PATH)
initial_population = [creator.Individual([int(d[list(d.keys())[0]]) for d in initial_solution])]

absolute_best_individual = None

while True:
    population = algorithms.eaMuPlusLambda(initial_population, toolbox, mu=len(initial_population), lambda_=len(initial_population),
                                          cxpb=0.5, mutpb=0.2, ngen=1)
    best_individual = tools.selBest(population, k=1)[0]
    fitness_values = calculate_fitness(best_individual)
    if absolute_best_individual is None or fitness_values < absolute_best_individual.fitness.values:
        absolute_best_individual = best_individual
        best_solution = [{"category_id": category_id, "warehouse_id": warehouse_id} for category_id, warehouse_id in enumerate(best_individual)]
        save_json_data(best_solution, os.path.join(BEST_EVER_DIR, 'best_ever_solution.json'))
        with open(os.path.join(BEST_EVER_DIR, 'best_ever_individual.log'), 'w') as log_file:
            log_file.write(f"Time Stamp: {os.path.join(BEST_EVER_DIR, 'best_ever_individual.log')}\n")
            log_file.write(f"Total Cost: {fitness_values[0]}\n")
            log_file.write(f"Inventory Utilization: {fitness_values[1]}\n")
            log_file.write(f"Sales Utilization: {fitness_values[2]}\n")
            log_file.write(f"Association: {fitness_values[3]}\n")